/*!
 Copyright (C) 2013-2015 by WebReflection

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.

 */
(function (window) {
    /*! (C) WebReflection Mit Style License */
    if (document.createEvent) return;
    var
        DUNNOABOUTDOMLOADED = true,
        READYEVENTDISPATCHED = false,
        ONREADYSTATECHANGE = 'onreadystatechange',
        DOMCONTENTLOADED = 'DOMContentLoaded',
        SECRET = '__IE8__' + Math.random(),
        // Object = window.Object,
        defineProperty = Object.defineProperty ||
            // just in case ...
            function (object, property, descriptor) {
                object[property] = descriptor.value;
            },
        defineProperties = Object.defineProperties ||
            // IE8 implemented defineProperty but not the plural...
            function (object, descriptors) {
                for (var key in descriptors) {
                    if (hasOwnProperty.call(descriptors, key)) {
                        try {
                            defineProperty(object, key, descriptors[key]);
                        } catch (o_O) {
                            if (window.console) {
                                console.log(key + ' failed on object:', object, o_O.message);
                            }
                        }
                    }
                }
            },
        getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
        hasOwnProperty = Object.prototype.hasOwnProperty,
        // here IE7 will break like a charm
        ElementPrototype = window.Element.prototype,
        TextPrototype = window.Text.prototype,
        // none of above native constructors exist/are exposed
        possiblyNativeEvent = /^[a-z]+$/,
        // ^ actually could probably be just /^[a-z]+$/
        readyStateOK = /loaded|complete/,
        types = {},
        div = document.createElement('div'),
        html = document.documentElement,
        removeAttribute = html.removeAttribute,
        setAttribute = html.setAttribute,
        valueDesc = function (value) {
            return {
                enumerable: true,
                writable: true,
                configurable: true,
                value: value
            };
        }
        ;

    function commonEventLoop(currentTarget, e, $handlers, synthetic) {
        for (var
                 handler,
                 continuePropagation,
                 handlers = $handlers.slice(),
                 evt = enrich(e, currentTarget),
                 i = 0, length = handlers.length; i < length; i++
        ) {
            handler = handlers[i];
            if (typeof handler === 'object') {
                if (typeof handler.handleEvent === 'function') {
                    handler.handleEvent(evt);
                }
            } else {
                handler.call(currentTarget, evt);
            }
            if (evt.stoppedImmediatePropagation) break;
        }
        continuePropagation = !evt.stoppedPropagation;
        /*
         if (continuePropagation && !synthetic && !live(currentTarget)) {
         evt.cancelBubble = true;
         }
         */
        return (
            synthetic &&
            continuePropagation &&
            currentTarget.parentNode
        ) ?
            currentTarget.parentNode.dispatchEvent(evt) :
            !evt.defaultPrevented
            ;
    }

    function commonDescriptor(get, set) {
        return {
            // if you try with enumerable: true
            // IE8 will miserably fail
            configurable: true,
            get: get,
            set: set
        };
    }

    function commonTextContent(protoDest, protoSource, property) {
        var descriptor = getOwnPropertyDescriptor(
            protoSource || protoDest, property
        );
        defineProperty(
            protoDest,
            'textContent',
            commonDescriptor(
                function () {
                    return descriptor.get.call(this);
                },
                function (textContent) {
                    descriptor.set.call(this, textContent);
                }
            )
        );
    }

    function enrich(e, currentTarget) {
        e.currentTarget = currentTarget;
        e.eventPhase = (
            // AT_TARGET : BUBBLING_PHASE
            e.target === e.currentTarget ? 2 : 3
        );
        return e;
    }

    function find(array, value) {
        var i = array.length;
        while (i-- && array[i] !== value);
        return i;
    }

    function getTextContent() {
        if (this.tagName === 'BR') return '\n';
        var
            textNode = this.firstChild,
            arrayContent = []
            ;
        while (textNode) {
            if (textNode.nodeType !== 8 && textNode.nodeType !== 7) {
                arrayContent.push(textNode.textContent);
            }
            textNode = textNode.nextSibling;
        }
        return arrayContent.join('');
    }

    function live(self) {
        return self.nodeType !== 9 && html.contains(self);
    }

    function onkeyup(e) {
        var evt = document.createEvent('Event');
        evt.initEvent('input', true, true);
        (e.srcElement || e.fromElement || document).dispatchEvent(evt);
    }

    function onReadyState(e) {
        if (!READYEVENTDISPATCHED && readyStateOK.test(
                document.readyState
            )) {
            READYEVENTDISPATCHED = !READYEVENTDISPATCHED;
            document.detachEvent(ONREADYSTATECHANGE, onReadyState);
            e = document.createEvent('Event');
            e.initEvent(DOMCONTENTLOADED, true, true);
            document.dispatchEvent(e);
        }
    }

    function pageGetter(coord) {
        var
            Dir = (coord === 'X' ? 'Left' : 'Top'),
            clientXY = 'client' + coord,
            clientLR = 'client' + Dir,
            scrollLR = 'scroll' + Dir,
            secretXY = '_@' + clientXY
            ;
        return function get() {
            /* jshint validthis:true */
            return this[secretXY] || (
                    this[secretXY] = (
                        this[clientXY] + (
                            html[scrollLR] || (document.body && document.body[scrollLR]) || 0
                        ) -
                        html[clientLR]
                    )
                );
        };
    }

    function setTextContent(textContent) {
        var node;
        while ((node = this.lastChild)) {
            this.removeChild(node);
        }
        /*jshint eqnull:true */
        if (textContent != null) {
            this.appendChild(document.createTextNode(textContent));
        }
    }

    function verify(self, e) {
        if (!e) {
            e = window.event;
        }
        if (!e.target) {
            e.target = e.srcElement || e.fromElement || document;
        }
        if (!e.timeStamp) {
            e.timeStamp = (new Date()).getTime();
        }
        return e;
    }

    // normalized textContent for:
    //  comment, script, style, text, title
    commonTextContent(
        window.HTMLCommentElement.prototype,
        ElementPrototype,
        'nodeValue'
    );

    commonTextContent(
        window.HTMLScriptElement.prototype,
        null,
        'text'
    );

    commonTextContent(
        TextPrototype,
        null,
        'nodeValue'
    );

    commonTextContent(
        window.HTMLTitleElement.prototype,
        null,
        'text'
    );

    defineProperty(
        window.HTMLStyleElement.prototype,
        'textContent',
        (function (descriptor) {
            return commonDescriptor(
                function () {
                    return descriptor.get.call(this.styleSheet);
                },
                function (textContent) {
                    descriptor.set.call(this.styleSheet, textContent);
                }
            );
        }(getOwnPropertyDescriptor(window.CSSStyleSheet.prototype, 'cssText')))
    );

    defineProperties(
        ElementPrototype,
        {
            // bonus
            textContent: {
                get: getTextContent,
                set: setTextContent
            },
            // http://www.w3.org/TR/ElementTraversal/#interface-elementTraversal
            firstElementChild: {
                get: function () {
                    for (var
                             childNodes = this.childNodes || [],
                             i = 0, length = childNodes.length;
                         i < length; i++
                    ) {
                        if (childNodes[i].nodeType == 1) return childNodes[i];
                    }
                }
            },
            lastElementChild: {
                get: function () {
                    for (var
                             childNodes = this.childNodes || [],
                             i = childNodes.length;
                         i--;
                    ) {
                        if (childNodes[i].nodeType == 1) return childNodes[i];
                    }
                }
            },
            oninput: {
                get: function () {
                    return this._oninput || null;
                },
                set: function (oninput) {
                    if (this._oninput) {
                        this.removeEventListener('input', this._oninput);
                        this._oninput = oninput;
                        if (oninput) {
                            this.addEventListener('input', oninput);
                        }
                    }
                }
            },
            previousElementSibling: {
                get: function () {
                    var previousElementSibling = this.previousSibling;
                    while (previousElementSibling && previousElementSibling.nodeType != 1) {
                        previousElementSibling = previousElementSibling.previousSibling;
                    }
                    return previousElementSibling;
                }
            },
            nextElementSibling: {
                get: function () {
                    var nextElementSibling = this.nextSibling;
                    while (nextElementSibling && nextElementSibling.nodeType != 1) {
                        nextElementSibling = nextElementSibling.nextSibling;
                    }
                    return nextElementSibling;
                }
            },
            childElementCount: {
                get: function () {
                    for (var
                             count = 0,
                             childNodes = this.childNodes || [],
                             i = childNodes.length; i--; count += childNodes[i].nodeType == 1
                    );
                    return count;
                }
            },
            /*
             // children would be an override
             // IE8 already supports them but with comments too
             // not just nodeType 1
             children: {
             get: function () {
             for(var
             children = [],
             childNodes = this.childNodes || [],
             i = 0, length = childNodes.length;
             i < length; i++
             ) {
             if (childNodes[i].nodeType == 1) {
             children.push(childNodes[i]);
             }
             }
             return children;
             }
             },
             */
            // DOM Level 2 EventTarget methods and events
            addEventListener: valueDesc(function (type, handler, capture) {
                if (typeof handler !== 'function' && typeof handler !== 'object') return;
                var
                    self = this,
                    ontype = 'on' + type,
                    temple = self[SECRET] ||
                        defineProperty(
                            self, SECRET, {value: {}}
                        )[SECRET],
                    currentType = temple[ontype] || (temple[ontype] = {}),
                    handlers = currentType.h || (currentType.h = []),
                    e, attr
                    ;
                if (!hasOwnProperty.call(currentType, 'w')) {
                    currentType.w = function (e) {
                        // e[SECRET] is a silent notification needed to avoid
                        // fired events during live test
                        return e[SECRET] || commonEventLoop(self, verify(self, e), handlers, false);
                    };
                    // if not detected yet
                    if (!hasOwnProperty.call(types, ontype)) {
                        // and potentially a native event
                        if (possiblyNativeEvent.test(type)) {
                            // do this heavy thing
                            try {
                                // TODO:  should I consider tagName too so that
                                //        INPUT[ontype] could be different ?
                                e = document.createEventObject();
                                // do not clone ever a node
                                // specially a document one ...
                                // use the secret to ignore them all
                                e[SECRET] = true;
                                // document a part if a node has never been
                                // added to any other node, fireEvent might
                                // behave very weirdly (read: trigger unspecified errors)
                                if (self.nodeType != 9) {
                                    /*jshint eqnull:true */
                                    if (self.parentNode == null) {
                                        div.appendChild(self);
                                    }
                                    if ((attr = self.getAttribute(ontype))) {
                                        removeAttribute.call(self, ontype);
                                    }
                                }
                                self.fireEvent(ontype, e);
                                types[ontype] = true;
                            } catch (meh) {
                                types[ontype] = false;
                                while (div.hasChildNodes()) {
                                    div.removeChild(div.firstChild);
                                }
                            }
                            if (attr != null) {
                                setAttribute.call(self, ontype, attr);
                            }
                        } else {
                            // no need to bother since
                            // 'x-event' ain't native for sure
                            types[ontype] = false;
                        }
                    }
                    if ((currentType.n = types[ontype])) {
                        self.attachEvent(ontype, currentType.w);
                    }
                }
                if (find(handlers, handler) < 0) {
                    handlers[capture ? 'unshift' : 'push'](handler);
                }
                if (type === 'input') {
                    self.attachEvent('onkeyup', onkeyup);
                }
            }),
            dispatchEvent: valueDesc(function (e) {
                var
                    self = this,
                    ontype = 'on' + e.type,
                    temple = self[SECRET],
                    currentType = temple && temple[ontype],
                    valid = !!currentType,
                    parentNode
                    ;
                if (!e.target) e.target = self;
                return (valid ? (
                        currentType.n /* && live(self) */ ?
                            self.fireEvent(ontype, e) :
                            commonEventLoop(
                                self,
                                e,
                                currentType.h,
                                true
                            )
                    ) : (
                        (parentNode = self.parentNode) /* && live(self) */ ?
                            parentNode.dispatchEvent(e) :
                            true
                    )), !e.defaultPrevented;
            }),
            removeEventListener: valueDesc(function (type, handler, capture) {
                if (typeof handler !== 'function' && typeof handler !== 'object') return;
                var
                    self = this,
                    ontype = 'on' + type,
                    temple = self[SECRET],
                    currentType = temple && temple[ontype],
                    handlers = currentType && currentType.h,
                    i = handlers ? find(handlers, handler) : -1
                    ;
                if (-1 < i) handlers.splice(i, 1);
            })
        }
    );

    /* this is not needed in IE8
     defineProperties(window.HTMLSelectElement.prototype, {
     value: {
     get: function () {
     return this.options[this.selectedIndex].value;
     }
     }
     });
     //*/

    // EventTarget methods for Text nodes too
    defineProperties(TextPrototype, {
        addEventListener: valueDesc(ElementPrototype.addEventListener),
        dispatchEvent: valueDesc(ElementPrototype.dispatchEvent),
        removeEventListener: valueDesc(ElementPrototype.removeEventListener)
    });

    defineProperties(
        window.XMLHttpRequest.prototype,
        {
            addEventListener: valueDesc(function (type, handler, capture) {
                var
                    self = this,
                    ontype = 'on' + type,
                    temple = self[SECRET] ||
                        defineProperty(
                            self, SECRET, {value: {}}
                        )[SECRET],
                    currentType = temple[ontype] || (temple[ontype] = {}),
                    handlers = currentType.h || (currentType.h = [])
                    ;
                if (find(handlers, handler) < 0) {
                    if (!self[ontype]) {
                        self[ontype] = function () {
                            var e = document.createEvent('Event');
                            e.initEvent(type, true, true);
                            self.dispatchEvent(e);
                        };
                    }
                    handlers[capture ? 'unshift' : 'push'](handler);
                }
            }),
            dispatchEvent: valueDesc(function (e) {
                var
                    self = this,
                    ontype = 'on' + e.type,
                    temple = self[SECRET],
                    currentType = temple && temple[ontype],
                    valid = !!currentType
                    ;
                return valid && (
                        currentType.n /* && live(self) */ ?
                            self.fireEvent(ontype, e) :
                            commonEventLoop(
                                self,
                                e,
                                currentType.h,
                                true
                            )
                    );
            }),
            removeEventListener: valueDesc(ElementPrototype.removeEventListener)
        }
    );

    defineProperties(
        window.Event.prototype,
        {
            bubbles: valueDesc(true),
            cancelable: valueDesc(true),
            preventDefault: valueDesc(function () {
                if (this.cancelable) {
                    this.defaultPrevented = true;
                    this.returnValue = false;
                }
            }),
            stopPropagation: valueDesc(function () {
                this.stoppedPropagation = true;
                this.cancelBubble = true;
            }),
            stopImmediatePropagation: valueDesc(function () {
                this.stoppedImmediatePropagation = true;
                this.stopPropagation();
            }),
            initEvent: valueDesc(function (type, bubbles, cancelable) {
                this.type = type;
                this.bubbles = !!bubbles;
                this.cancelable = !!cancelable;
                if (!this.bubbles) {
                    this.stopPropagation();
                }
            }),
            pageX: {get: pageGetter('X')},
            pageY: {get: pageGetter('Y')}
        }
    );

    defineProperties(
        window.HTMLDocument.prototype,
        {
            defaultView: {
                get: function () {
                    return this.parentWindow;
                }
            },
            textContent: {
                get: function () {
                    return this.nodeType === 11 ? getTextContent.call(this) : null;
                },
                set: function (textContent) {
                    if (this.nodeType === 11) {
                        setTextContent.call(this, textContent);
                    }
                }
            },
            addEventListener: valueDesc(function (type, handler, capture) {
                var self = this;
                ElementPrototype.addEventListener.call(self, type, handler, capture);
                // NOTE:  it won't fire if already loaded, this is NOT a $.ready() shim!
                //        this behaves just like standard browsers
                if (
                    DUNNOABOUTDOMLOADED &&
                    type === DOMCONTENTLOADED && !readyStateOK.test(
                        self.readyState
                    )
                ) {
                    DUNNOABOUTDOMLOADED = false;
                    self.attachEvent(ONREADYSTATECHANGE, onReadyState);
                    /* global top */
                    if (window == top) {
                        (function gonna(e) {
                            try {
                                self.documentElement.doScroll('left');
                                onReadyState();
                            } catch (o_O) {
                                setTimeout(gonna, 50);
                            }
                        }());
                    }
                }
            }),
            dispatchEvent: valueDesc(ElementPrototype.dispatchEvent),
            removeEventListener: valueDesc(ElementPrototype.removeEventListener),
            createEvent: valueDesc(function (Class) {
                var e;
                if (Class !== 'Event') throw new Error('unsupported ' + Class);
                e = document.createEventObject();
                e.timeStamp = (new Date()).getTime();
                return e;
            })
        }
    );

    defineProperties(
        window.Window.prototype,
        {
            getComputedStyle: valueDesc(function () {

                var // partially grabbed from jQuery and Dean's hack
                    notpixel = /^(?:[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/,
                    position = /^(top|right|bottom|left)$/,
                    re = /\-([a-z])/g,
                    place = function (match, $1) {
                        return $1.toUpperCase();
                    }
                    ;

                function ComputedStyle(_) {
                    this._ = _;
                }

                ComputedStyle.prototype.getPropertyValue = function (name) {
                    var
                        el = this._,
                        style = el.style,
                        currentStyle = el.currentStyle,
                        runtimeStyle = el.runtimeStyle,
                        result,
                        left,
                        rtLeft
                        ;
                    name = (name === 'float' ? 'style-float' : name).replace(re, place);
                    result = currentStyle ? currentStyle[name] : style[name];
                    if (notpixel.test(result) && !position.test(name)) {
                        left = style.left;
                        rtLeft = runtimeStyle && runtimeStyle.left;
                        if (rtLeft) {
                            runtimeStyle.left = currentStyle.left;
                        }
                        style.left = name === 'fontSize' ? '1em' : result;
                        result = style.pixelLeft + 'px';
                        style.left = left;
                        if (rtLeft) {
                            runtimeStyle.left = rtLeft;
                        }
                    }
                    /*jshint eqnull:true */
                    return result == null ?
                        result : ((result + '') || 'auto');
                };

                // unsupported
                function PseudoComputedStyle() {
                }

                PseudoComputedStyle.prototype.getPropertyValue = function () {
                    return null;
                };

                return function (el, pseudo) {
                    return pseudo ?
                        new PseudoComputedStyle(el) :
                        new ComputedStyle(el);
                };

            }()),

            addEventListener: valueDesc(function (type, handler, capture) {
                var
                    self = window,
                    ontype = 'on' + type,
                    handlers
                    ;
                if (!self[ontype]) {
                    self[ontype] = function (e) {
                        return commonEventLoop(self, verify(self, e), handlers, false);
                    };
                }
                handlers = self[ontype][SECRET] || (
                        self[ontype][SECRET] = []
                    );
                if (find(handlers, handler) < 0) {
                    handlers[capture ? 'unshift' : 'push'](handler);
                }
            }),
            dispatchEvent: valueDesc(function (e) {
                var method = window['on' + e.type];
                return method ? method.call(window, e) !== false && !e.defaultPrevented : true;
            }),
            removeEventListener: valueDesc(function (type, handler, capture) {
                var
                    ontype = 'on' + type,
                    handlers = (window[ontype] || Object)[SECRET],
                    i = handlers ? find(handlers, handler) : -1
                    ;
                if (-1 < i) handlers.splice(i, 1);
            })
        }
    );

    (function (styleSheets, HTML5Element, i) {
        for (i = 0; i < HTML5Element.length; i++) document.createElement(HTML5Element[i]);
        if (!styleSheets.length) document.createStyleSheet('');
        styleSheets[0].addRule(HTML5Element.join(','), 'display:block;');
    }(document.styleSheets, ['header', 'nav', 'section', 'article', 'aside', 'footer']));

    /**
     * 扩展Object.create方法
     */
    if (!Object.create) {
        Object.create = function (o) {
            function F() {
            };
            F.prototype = o;
            return new F();
        };
    }

    /**
     * 扩展startsWith和endsWith方法
     */
    String.prototype.startsWith = String.prototype.startsWith || function (prefix) {
            return this.slice(0, prefix.length) === prefix;
        };
    String.prototype.endsWith = String.prototype.endsWith || function (suffix) {
            return this.indexOf(suffix, this.length - suffix.length) !== -1;
        };

}(this.window || global));

/**
 * IE8支持Object.keys
 * 遗憾的是，不能支持getOwnPropertyNames，因为IE8使用的ECMAScript3不支持遍历non-enumerable的属性
 */
var DONT_ENUM = "propertyIsEnumerable,isPrototypeOf,hasOwnProperty,toLocaleString,toString,valueOf,constructor".split(","),
    hasOwn = ({}).hasOwnProperty;
for (var i in {
    toString: 1
}) {
    DONT_ENUM = false;
}
Object.keys = Object.keys || function (obj) {
        var result = [];
        for (var key in obj) if (hasOwn.call(obj, key)) {
            result.push(key);
        }
        if (DONT_ENUM && obj) {
            for (var i = 0; key = DONT_ENUM[i++];) {
                if (hasOwn.call(obj, key)) {
                    result.push(key);
                }
            }
        }
        return result;
    };